package jmine.tec.rpc.server.provider;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.rpc.RPCMessages;
import jmine.tec.rpc.common.RPCException;
import jmine.tec.rpc.common.entity.EntityRepresentation;
import jmine.tec.rpc.common.entity.EntityRest;
import jmine.tec.rpc.common.metadata.RestServiceMetadata;
import jmine.tec.rpc.common.provider.RestServiceDiscovery;
import jmine.tec.rpc.common.provider.ServiceInfo;
import jmine.tec.rpc.common.provider.ServiceUnavailableException;
import jmine.tec.rpc.server.base.BaseEntityRestService;
import jmine.tec.rpc.server.entity.EntityRestMapping;
import bancosys.tec.persist.Persistable;

/**
 * Implementação de RestServiceDiscovery
 * 
 * @author lundberg
 */
public class ServerRestServiceDiscovery implements RestServiceDiscovery {

    private Map<Class<?>, Class<? extends EntityRest>> representationService =
            new ConcurrentHashMap<Class<?>, Class<? extends EntityRest>>();

    private Map<Class<?>, String> servicePrefix = new ConcurrentHashMap<Class<?>, String>();

    private EntityRestMapping entityRestMapping;

    /**
     * {@inheritDoc}
     */
    public <R extends EntityRepresentation> ServiceInfo<? extends EntityRest<R>> getRepresentationServiceInfo(Class<R> representation)
            throws ServiceUnavailableException {
        return this.pack(this.findServiceForRepresentation(representation));
    }

    /**
     * {@inheritDoc}
     */
    public <S> ServiceInfo<S> getServiceInfo(Class<S> service) throws ServiceUnavailableException {
        if (!this.servicePrefix.containsKey(service)) {
            throw new RPCException(RPCMessages.SERVICE_NOT_FOUND.create(service.getName()));
        }
        return this.pack(service);
    }

    /**
     * Empacota as informações do serviço
     * 
     * @param <S> <S>
     * @param service service
     * @return ServiceInfo
     */
    private <S> ServiceInfo<S> pack(Class<S> service) {
        return new ServiceInfo<S>(service, this.servicePrefix.get(service));
    }

    /**
     * Encontra o serviço adequado para a representação
     * 
     * @param <R> tipo de representação
     * @param representation representation
     * @return EntityRestService
     * @throws ServiceUnavailableException ServiceUnavailableException
     */
    @SuppressWarnings("unchecked")
    private <R extends EntityRepresentation> Class<? extends EntityRest<R>> findServiceForRepresentation(Class<R> representation)
            throws ServiceUnavailableException {
        if (!this.representationService.containsKey(representation)) {
            throw new ServiceUnavailableException(RPCMessages.ENTITY_REST_SERVICE_NOT_FOUND.create(representation.getName()));
        }
        return (Class<? extends EntityRest<R>>) this.representationService.get(representation);
    }

    /**
     * Registra um serviço como disponível
     * 
     * @param service service
     * @param implementor implementor
     * @param prefix prefix
     */
    public void register(Class<?> service, Object implementor, String prefix) {
        RestServiceMetadata.verifyService(service);
        if (!service.isInstance(implementor)) {
            throw new RPCException(RPCMessages.IMPLEMENTOR_DOES_NOT_IMPLEMENT_GIVEN_SERVICE.create(implementor, service.getName()));
        }
        this.servicePrefix.put(service, prefix);
        if (EntityRest.class.isAssignableFrom(service)) {
            EntityRest entityImplementor = (EntityRest) implementor;
            Class<? extends EntityRest> entityService = service.asSubclass(EntityRest.class);
            this.representationService.put(entityImplementor.getRepresentationClass(), entityService);

            // Registro o mapeamento entre as classes de representação e entidade.
            if (BaseEntityRestService.class.isAssignableFrom(implementor.getClass())) {

                BaseEntityRestService<? extends EntityRepresentation, ? extends Persistable> restService =
                        (BaseEntityRestService<?, ?>) implementor;

                Class<? extends EntityRepresentation> representationClass = restService.getRepresentationClass();
                Class<? extends Persistable> persistableClass = restService.getEntityClass();

                this.entityRestMapping.register(representationClass, persistableClass);
            }

        }
    }

    /**
     * Set EntityRestMapping when testing.
     * 
     * @param entityRestMap An EntityRestMapping object.
     */
    public void setEntityRestMapping(EntityRestMapping entityRestMap) {
        this.entityRestMapping = entityRestMap;
    }

}
